#include "SchmidtCAM.h"

#define PORT            1001
#define LISTEN_QUEUE    16
#define BUFFER_SIZE     1024

time_t arm_sys_time = 0;
struct timeval start;
struct timeval end;

char timestamp0[26]; // 'op' + 23+'\n' = 26
char timestamp1[26]; // 'cl' + 23+'\n' = 26
char timestamp[51];


////////////////////////////////////////////////////////////////////////////////////
// 备份CCD驱动时序参数
// backup vtrans parameters
int vtrans_num_bak; //      = mcfg->config_ccd_drive->vtrans_num;
int imgCLK1_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK1_cnt_min;
int imgCLK1_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK1_cnt_max;
int imgCLK2_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK2_cnt_min;
int imgCLK2_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK2_cnt_max;
int imgCLK3_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK3_cnt_min;
int imgCLK3_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK3_cnt_max;
int imgCLK4_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK4_cnt_min;
int imgCLK4_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK4_cnt_max;
int imgCLKx_cnt_MAX_bak;    // = mcfg->config_ccd_drive->imgCLKx_cnt_MAX;
int vtrans_state_cnt_max_bak;   // = mcfg->config_ccd_drive->vtrans_state_cnt_max;

// backup htrans parameters
int htrans_num_bak; // = mcfg->config_ccd_drive->htrans_num;
///////////////////////////////////////////////////////////////////////////////////

uint32_t cStatus = 0;   // 记录当前的工作状态，有且仅有两种可能得状态：
                        // 0: 表示空闲，没有曝光或者读出
                        // 1: 表示接收到ICS的拍摄指令了，正在执行中

char log_file[1024];
FILE *fp_log_file;

typedef struct _SockInfo {
    struct sockaddr_in addr;
    int fd;
    SchmidtCAM_Config *scfg;
}SockInfo;

SockInfo infos[16];

SPI_CONFIG *spi_config_daisy;// = (SPI_CONFIG*)malloc(sizeof(SPI_CONFIG));

pthread_mutex_t abort_mutex;

void *serverForClient(void * arg);
void abortExposure(void *arg);

static int global_abort_exposure = 0;

int ccd_exposure_with_abort(double t_exp, 
                            uint32_t *gpio_reg_ctrl, 
                            uint32_t *gpio_reg_status, 
                            uint32_t *gpio_reg_pl_ps_ddr_cnt, 
                            uint32_t n_row,
                            uint32_t n_col,
                            int n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                            int fast_read_opt,
                            int is_dark,
                            char *filename,
                            char *log_shutter_fname,
                            char *log_camera_fname,
                            SchmidtCAM_Config *mcfg );


int main( int argc, char *argv[] ) {

    if( argc < 2 ){
        printf("\n*********************************************\n");
        printf("usage: %s logfilename.txt\n", argv[0]);
        printf("*********************************************\n");
        return 0;
    }

    pthread_mutex_init(&abort_mutex, PTHREAD_MUTEX_TIMED_NP);

    //=================================================
    //  导入所有配置参数
    //=================================================
    char config_dir[1024];      // 保存参数配置文件所在的目录(相对路径)

    FILE *fp_wkdir = fopen(".WKDIR", "r");
    fscanf(fp_wkdir, "%s", config_dir);
    fclose(fp_wkdir);

    SchmidtCAM_Config SCAM_Config;
    init_SchmidtCAM_Config( &SCAM_Config, config_dir );

    spi_config_daisy = (SPI_CONFIG*)malloc(sizeof(SPI_CONFIG));
    setup_SPI_by_device_name( spi_config_daisy, "daisy" );

    // update log_file[1024];
    sprintf(log_file, "log/%s", argv[1]);
    fprintf(stdout, "* --> log file: %s\n", log_file);
    fp_log_file = fopen(log_file, "a+");
    

    // backup vtrans parameters
    vtrans_num_bak          = SCAM_Config.config_ccd_drive->vtrans_num;
    imgCLK1_cnt_min_bak     = SCAM_Config.config_ccd_drive->imgCLK1_cnt_min;
    imgCLK1_cnt_max_bak     = SCAM_Config.config_ccd_drive->imgCLK1_cnt_max;
    imgCLK2_cnt_min_bak     = SCAM_Config.config_ccd_drive->imgCLK2_cnt_min;
    imgCLK2_cnt_max_bak     = SCAM_Config.config_ccd_drive->imgCLK2_cnt_max;
    imgCLK3_cnt_min_bak     = SCAM_Config.config_ccd_drive->imgCLK3_cnt_min;
    imgCLK3_cnt_max_bak     = SCAM_Config.config_ccd_drive->imgCLK3_cnt_max;
    imgCLK4_cnt_min_bak     = SCAM_Config.config_ccd_drive->imgCLK4_cnt_min;
    imgCLK4_cnt_max_bak     = SCAM_Config.config_ccd_drive->imgCLK4_cnt_max;
    imgCLKx_cnt_MAX_bak     = SCAM_Config.config_ccd_drive->imgCLKx_cnt_MAX;
    vtrans_state_cnt_max_bak= SCAM_Config.config_ccd_drive->vtrans_state_cnt_max;
    
    // backup htrans parameters
    htrans_num_bak          = SCAM_Config.config_ccd_drive->htrans_num;

    //=========================================================================
    // GPIO 
    // uint32_t *gpio_flt_ctrl;        // ADDR_Float_Point_CTRL
    // uint32_t *gpio_flt_data;        // ADDR_Float_Point_DATA
    // uint32_t *gpio_in;              // ADDR_GPIO_IN
    // uint32_t *gpio_pl_ps;           // ADDR_PL_PS_DDR_CNT
    // uint32_t *gpio_out;             // ADDR_GPIO_OUT
    // uint32_t *gpio_out2;            // ADDR_GPIO_OUT
    // uint32_t *gpio_spi_ps_ctrl;     // ADDR_GPIO_SPI_PS_CTRL
    // uint32_t *gpio_spi_ps_data;     // ADDR_GPIO_SPI_PS_DATA

    int fd_gpio = open("/dev/mem", O_RDWR | O_SYNC);

    uint32_t *gpio_flt_ctrl     = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_Float_Point_CTRL);
    uint32_t *gpio_flt_data     = gpio_flt_ctrl + 2;

    uint32_t *gpio_in           = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_GPIO_IN);
    uint32_t *gpio_pl_ps        = gpio_in + 2;

    uint32_t *gpio_out          = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_GPIO_OUT);
    uint32_t *gpio_out2         = gpio_out + 2;

    uint32_t *gpio_spi_ps_ctrl  = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_GPIO_SPI_PS_CTRL);
    uint32_t *gpio_spi_ps_data  = gpio_spi_ps_ctrl + 2;

    SCAM_Config.gpio_flt_ctrl   = gpio_flt_ctrl;
    SCAM_Config.gpio_flt_data   = gpio_flt_data;
    SCAM_Config.gpio_in         = gpio_in;
    SCAM_Config.gpio_pl_ps      = gpio_pl_ps;
    SCAM_Config.gpio_out        = gpio_out;
    SCAM_Config.gpio_out2       = gpio_out2;
    SCAM_Config.gpio_spi_ps_ctrl= gpio_spi_ps_ctrl;
    SCAM_Config.gpio_spi_ps_data= gpio_spi_ps_data;
    //=========================================================================
    // reset status of shutter & camera
    printf("**** reset shutter status to ready (0) !\n");
    update_shutter_status(0, SCAM_Config.log_shutter_fname);
    
    printf("**** reset camera status to ready (0) !\n");
    update_camera_status(0, SCAM_Config.log_camera_fname);

    //=================================================
    //      SOCKET
    //=================================================

    // initialize client Info
    int sock_max_size = sizeof(infos) / sizeof(infos[0]);
    int i;
    for( i=0; i<sock_max_size; i++ ){
        bzero(&infos[i], sizeof(infos[i]));
        infos[i].fd     = -1;
        infos[i].scfg   = NULL;
    }

    //初始化socket
    int sock_fd = socket(AF_INET,SOCK_STREAM,0);
    int on = 1;                                         // Enable address reuse
    setsockopt( sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );

    struct sockaddr_in server_sockaddr;                 // socket service
    struct sockaddr_in client_addr;                     // socket client
    socklen_t sin_size = sizeof(struct sockaddr_in);

    memset( &server_sockaddr, 0, sizeof(server_sockaddr));
    bzero( &client_addr, sizeof(client_addr) );

    server_sockaddr.sin_family      = AF_INET;
    server_sockaddr.sin_port        = htons(PORT);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    ///bind成功返回0出错返回-1
    if( bind(sock_fd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1 )
    {
        fprintf(stderr, "[SchmidtCAM] Failed to bind socket, quit.\n");
        exit(-1);
    }

    ///listen成功返回0出错返回-1
    if( listen( sock_fd, LISTEN_QUEUE ) < -1 )
    {
        fprintf(stderr, "[SchmidtCAM]  listen function failed.\n");
        exit(-1);
    }

    printf("[SchmidtCAM] start socket listening Port: %d ...\n", PORT);

    while(1) {

        SockInfo *pinfo = NULL;

        for( i=0; i<sock_max_size; i++ ){
            if( infos[i].fd == -1 ){
                pinfo       = &infos[i];
                // pinfo->mcfg = &MCAM_Config;
                break;
            }
        }

        int cfd = accept( sock_fd, (struct sockaddr *)&client_addr, &sin_size ); //accpet
        
        if( -1 == cfd ) {
            perror("[SchmidtCAM] socket accept failed! \n");
            fprintf(stdout, "[SchmidtCAM] continue trying to accept socket connections ...\n");
            continue;
        }

        pinfo->fd   = cfd;
        pinfo->scfg = &SCAM_Config;

        if( pinfo != NULL ){
            fprintf(stdout, "\n[SchmidtCAM: main thread] --> SockInfo infos[%2d] is activated\n", i);
            fprintf(stdout, "[SchmidtCAM: main thread] --> socket fd: %d\n\n", cfd);
        }

        // create new thread
        // if( cStatus == 0 ){
            pthread_t tid;
            pthread_create(&tid, NULL, serverForClient, pinfo);
            pthread_detach(tid);
        // }

        // usleep(1000);
        usleep(100);
    }

    close(sock_fd);

    fclose(fp_log_file);

    free(spi_config_daisy);

    return 0;
}

void *serverForClient(void *arg) {

    // cStatus == 1;   // a new thread is created, change cStatus to 1
    
    //////////////////////////////////////////////////////////
    // necessay info recived from LabView ICS

    char tag[16];

    int fout_name_is_ready = 0;
    char fout_name[128];

    int exp_t_is_ready = 0;
    double exp_t = -1;

    int img_is_dark = 0;
    //////////////////////////////////////////////////////////

    SockInfo           *pinfo  = (SockInfo*)arg;
    SchmidtCAM_Config *scfg   = pinfo->scfg;
    
    char recvbuf[BUFFER_SIZE];
    char sendbuf[BUFFER_SIZE];

    int new_fd = pinfo->fd;

    while(1) {
        
        bzero(recvbuf, sizeof(recvbuf));
        bzero(sendbuf, sizeof(sendbuf));

        // print client IP/port
        // char ip[32];
        // fprintf(stdout, "Client IP: %s, port: %d ==> ",
        //     inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)),
        //     ntohs(pinfo->addr.sin_port));

        socklen_t len = recv(new_fd, recvbuf, sizeof(recvbuf), 0);

        if( len > 0 ){
            // fputs(recvbuf,stdout);
            fprintf(stdout, "[command from ICS] %s\n", recvbuf);

            if( strcmp(recvbuf, "exposure") == 0 ){

                pthread_mutex_lock(&abort_mutex);
                global_abort_exposure = 0;
                pthread_mutex_unlock(&abort_mutex);
                
                strcpy(sendbuf, "expostarted");
                send(new_fd, sendbuf, strlen(sendbuf), 0);

                if( (1 == fout_name_is_ready) && (1 == exp_t_is_ready) ){

                gettimeofday(&start, NULL);
/*                #if defined(_ENABLE_PINNING_BEFORE_EXPOSURE_)*/
/*                    fprintf(stdout, "[CCD_PINNING] --> start\n");*/
/*                    socket_pinning( mcfg->gpio_out, 1 );*/
/*                    usleep(200000); // 200ms*/
/*                    socket_pinning( mcfg->gpio_out, 0 );*/
/*                    fprintf(stdout, "[CCD_PINNING] --> finish\n");*/
/*                #endif*/
                

                // Trig_Readout( mcfg->gpio_out );
                // fprintf(stdout, "[serverForClient] GPIO_ADDR : 0X%X\n", mcfg->gpio_out);

                #if defined(_ENABLE_REFRESH_BEFORE_EXPOSURE_)

                    // set vtrans parameters to temporary values
                    scfg->config_ccd_drive->vtrans_num      = 4650;  // 4700 --> 4650
                    scfg->config_ccd_drive->imgCLK1_cnt_min = 600;
                    scfg->config_ccd_drive->imgCLK1_cnt_max = 1200;
                    scfg->config_ccd_drive->imgCLK2_cnt_min = 300;
                    scfg->config_ccd_drive->imgCLK2_cnt_max = 900;
                    scfg->config_ccd_drive->imgCLK3_cnt_min = 600;
                    scfg->config_ccd_drive->imgCLK3_cnt_max = 1200;
                    scfg->config_ccd_drive->imgCLK4_cnt_min = 300;
                    scfg->config_ccd_drive->imgCLK4_cnt_max = 900;
                    scfg->config_ccd_drive->imgCLKx_cnt_MAX = 1450;
                    scfg->config_ccd_drive->vtrans_state_cnt_max = 100;
                    
                    // set htrans parameters to temporary values
                    scfg->config_ccd_drive->htrans_num = 1;

                    // set htrans_num to 1
                    socket_config_ccd_drive(scfg, 
                                            scfg->gpio_flt_ctrl, 
                                            scfg->gpio_flt_data,
                                            scfg->gpio_in,
                                            scfg->gpio_pl_ps,
                                            scfg->gpio_out,
                                            scfg->gpio_out2,
                                            scfg->gpio_spi_ps_ctrl,
                                            scfg->gpio_spi_ps_data );

                    fprintf(stdout, "[CCD_DRIVE] set to fast refresh mode.\n");
                    // sleep(1);

                    // fast refresh
                    ccd_refresh( scfg->gpio_out, 
                                 scfg->gpio_in, 
                                 scfg->gpio_pl_ps, 
                                 scfg->n_row,
                                 scfg->n_col,
                                 scfg->n_bytes,
                                 0,
                                 "" );


                    // reset vtrans parameters
                    scfg->config_ccd_drive->vtrans_num      = vtrans_num_bak;  // 4650 --> 4700
                    scfg->config_ccd_drive->imgCLK1_cnt_min = imgCLK1_cnt_min_bak;
                    scfg->config_ccd_drive->imgCLK1_cnt_max = imgCLK1_cnt_max_bak;
                    scfg->config_ccd_drive->imgCLK2_cnt_min = imgCLK2_cnt_min_bak;
                    scfg->config_ccd_drive->imgCLK2_cnt_max = imgCLK2_cnt_max_bak;
                    scfg->config_ccd_drive->imgCLK3_cnt_min = imgCLK3_cnt_min_bak;
                    scfg->config_ccd_drive->imgCLK3_cnt_max = imgCLK3_cnt_max_bak;
                    scfg->config_ccd_drive->imgCLK4_cnt_min = imgCLK4_cnt_min_bak;
                    scfg->config_ccd_drive->imgCLK4_cnt_max = imgCLK4_cnt_max_bak;
                    scfg->config_ccd_drive->imgCLKx_cnt_MAX = imgCLKx_cnt_MAX_bak;
                    scfg->config_ccd_drive->vtrans_state_cnt_max = vtrans_state_cnt_max_bak;

                    // reset htrans_num
                    scfg->config_ccd_drive->htrans_num = htrans_num_bak;

                    socket_config_ccd_drive(scfg, 
                                            scfg->gpio_flt_ctrl, 
                                            scfg->gpio_flt_data,
                                            scfg->gpio_in,
                                            scfg->gpio_pl_ps,
                                            scfg->gpio_out,
                                            scfg->gpio_out2,
                                            scfg->gpio_spi_ps_ctrl,
                                            scfg->gpio_spi_ps_data );

                    fprintf(stdout, "[CCD_DRIVE] reset to nomal readout mode.\n");
                    // sleep(1);
                #endif
                    

                    gettimeofday(&end, NULL);

                    // write total refresh time into log
                    char log_msg[128];
                    sprintf(log_msg, "total refresh time used is %10.6f seconds", time_diff(&start,&end));
                    write_log(fp_log_file, log_msg);

                    ccd_exposure_with_abort(exp_t, 
                                            scfg->gpio_out, 
                                            scfg->gpio_in, 
                                            scfg->gpio_pl_ps, 
                                            scfg->n_row,
                                            scfg->n_col,
                                            scfg->n_bytes,
                                            0,
                                            img_is_dark,
                                            fout_name,
                                            scfg->log_shutter_fname,
                                            scfg->log_camera_fname,
                                            scfg );

                    fflush(fp_log_file);

                    if( global_abort_exposure == 1 ){
                        strcpy(sendbuf, "expoaborted");
                        fprintf(stdout, "++> sent expoaborted\n");     // 曝光终止
                    } else {
                        strcpy(sendbuf, "readoutdone");
                        fprintf(stdout, "**> sent readoutdone\n");     // 曝光正常结束
                    }

                    sprintf(timestamp, "%s%s", timestamp0, timestamp1);
                    send(new_fd, timestamp, strlen(timestamp), 0);
                    
                    // for debug
                    fprintf(stdout, "sent new timestamps: %s\n", timestamp);

                    //usleep(1000);
                    //send(new_fd, sendbuf, strlen(sendbuf), 0);
                    exp_t_is_ready  = 0;
                    img_is_dark     = 0;

                    // pthread_mutex_lock(&abort_mutex);
                    // global_abort_exposure = 0;
                    // pthread_mutex_unlock(&abort_mutex);
                } else {
                    fprintf(stdout, "> fout_name is not ready, exposure will no be executed ...\n");
                }

                if( global_abort_exposure == 1 )
                    break;
            }
            else if( strcmp(recvbuf, "powerup") == 0 ){
                // socket_power_up(    mcfg, 
                //                     gpio_flt_ctrl, 
                //                     gpio_flt_data,
                //                     gpio_in,
                //                     gpio_pl_ps,
                //                     gpio_out,
                //                     gpio_out2,
                //                     gpio_spi_ps_ctrl,
                //                     gpio_spi_ps_data );

                sleep(1);

                strcpy(sendbuf, "powerup_ok_");
                send(new_fd, sendbuf, strlen(sendbuf), 0);
            }
            else if( strcmp(recvbuf, "powerdown") == 0 ){
                // socket_power_down(  mcfg, 
                //                     gpio_flt_ctrl, 
                //                     gpio_flt_data,
                //                     gpio_in,
                //                     gpio_pl_ps,
                //                     gpio_out,
                //                     gpio_out2,
                //                     gpio_spi_ps_ctrl,
                //                     gpio_spi_ps_data );

                sleep(1);

                strcpy(sendbuf, "powerdownok");
                send(new_fd, sendbuf, strlen(sendbuf), 0);
            }
            else if( strcmp(recvbuf, "imgisdark") == 0 ){
                img_is_dark = 1;
                strcpy(sendbuf, "dakreceived");
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                fprintf(stdout, "> sent dakreceived to ICS\n");
            }
            else if( strcmp(recvbuf, "imgnodark") == 0 ){
                img_is_dark = 0;
                strcpy(sendbuf, "dakreceived");
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                fprintf(stdout, "**> sent dakreceived to ICS\n");
            }
            else if( strlen(recvbuf) == 9 ){

                // fprintf(stdout, "[SoC Socket server] received tag: %s\n", recvbuf);
                sprintf(tag, "%s", recvbuf);
                // fprintf(stdout, "[server] len(recvbuf) = %u\n", strlen(recvbuf));

                int isExpTime = isExpTimeTag( tag );

                if( 1 == isExpTime ){
                    exp_t = extractExpTime( tag );
                    fprintf(stdout, "**> from ICS recieved a exposure time tag, and exp_t = %10.5f\n", exp_t);
                    exp_t_is_ready = 1;
                    strcpy(sendbuf, "expreceived");
                } else {
                    fprintf(stdout, "**> from ICS recieved a date tag: %s.\n", recvbuf);
                    sprintf(fout_name, "tmp/%s.bin", recvbuf);
                    fout_name_is_ready = 1;
                    strcpy(sendbuf, "tagreceived");
                }
                
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                fprintf(stdout, "**> sent %s to ICS\n", sendbuf);
            }
            else if( strcmp(recvbuf, "abortexp") == 0 ){
                // abort exposure ...
                abortExposure(arg);
                strcpy(sendbuf, "abtreceived");
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                fprintf(stdout, "++> sent %s to ICS\n", sendbuf);
                sleep(1);
                break;
            }
            else if( strcmp(recvbuf, "exit") == 0 ){

                strcpy(sendbuf, "exit_ok");
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                fprintf(stdout, "++> send %s to ICS\n", sendbuf);
                break;
            }
            else if( strcmp(recvbuf, "a_exit") == 0 ){
                break;
            }
            else if( strlen(recvbuf) == 10 ){
                fprintf(stdout, "> synchronize SoC linux system time...\n");
                arm_sys_time = atoi(recvbuf);

                struct timeval tv;
                tv.tv_sec = arm_sys_time;//+8*3600;
                tv.tv_usec= 0;
                settimeofday(&tv, NULL);
                time_t t_now = time(NULL);
                fprintf(stdout, "> %s\n", ctime(&t_now));
            }
            else{
                fprintf(stdout,"\n*******************************************\n");
                fprintf(stdout, "(%s): **** unkown command: %s ****\n", __func__, recvbuf);
                fprintf(stdout,"*******************************************\n");
                continue;
            }

        }

        // usleep(1000);   // 1ms sleep
        usleep(500);   // 0.5ms sleep
    }

    close(pinfo->fd);
    pinfo->fd   = -1;
    pinfo->scfg = NULL;

    close(new_fd);

    fprintf(stdout,"##> thread: %lu closed.\n", pthread_self());

    // cStatus == 0;   // this thread has finised its job, change cStatus to 0

    return NULL;
}


void abortExposure(void *arg){
    pthread_t ctid = pthread_self();

    pthread_mutex_lock(&abort_mutex);
    global_abort_exposure = 1;
    pthread_mutex_unlock(&abort_mutex);

    fprintf(stdout, "[THREAD: %lu] send 'ABORT EXPOSURE' signal!\n", ctid);
}

// 原先在RedOut.c源文件中，但是因为需要使用全局变量的原因，转移到这边
// ccd_exposure在ccd_readout的基础上增加了对快门的控制，其余功能一致
int ccd_exposure_with_abort(double t_exp, 
                            uint32_t *gpio_reg_ctrl,            // gpio_out
                            uint32_t *gpio_reg_status,          // gpio_in
                            uint32_t *gpio_reg_pl_ps_ddr_cnt,   // gpio_pl_ps
                            uint32_t n_row,
                            uint32_t n_col,
                            int n_bytes,
                            int fast_read_opt,
                            int is_dark,
                            char *filename,
                            char *log_shutter_fname,
                            char *log_camera_fname,
                            SchmidtCAM_Config *mcfg ){

    // char log_msg[128];
    update_camera_status(2, log_camera_fname);

    /* open shutter for a given time interval and then close it */
    
    if( is_dark == 1 ){
        dark_frame_with_abort(t_exp);
    } else {
        if( t_exp > 0 ){
            char log_msg[128];
            sprintf(log_msg, "start taking an exposure of %g seconds ...", t_exp);
            write_log(fp_log_file, log_msg);

            fprintf(stdout, "[%s]--> start exposuring for %g seconds ...\n", __func__, t_exp);
            update_shutter_status(1, log_shutter_fname);
            fprintf(stdout, "[%s]--> open shutter ...\n", __func__);

            bonn_shutter_exposure_with_abort( gpio_reg_ctrl, 
                                              gpio_reg_status,
                                              t_exp );
            fprintf(stdout, "[%s]--> close shutter ...\n", __func__);
            update_shutter_status(0, log_shutter_fname);

            write_log(fp_log_file, "exposure time is reached.");
        }
        else{
            write_log(fp_log_file, "start taking a bias frame ...");
            gen_timestamp_op(timestamp0);
            gen_timestamp_cl(timestamp1);
        }
    }

    if( global_abort_exposure == 0 ){
        /* readout */
        update_camera_status(3, log_camera_fname);

        fprintf(stdout, "[%s]--> CCD readout starts ...\n", __func__);
        write_log(fp_log_file, "CCD readout start ...");

        gettimeofday(&start, NULL);

        ccd_readout_fp( gpio_reg_ctrl, 
                        gpio_reg_status, 
                        gpio_reg_pl_ps_ddr_cnt,
                        n_row,
                        n_col,
                        n_bytes,
                        fast_read_opt,
                        filename,
                        fp_log_file );
        
        gettimeofday(&end, NULL);

        double t_diff = time_diff(&start,&end);
        fprintf(stdout, "[%s]--> CCD readout ends, total time: %12.6f seconds\n", __func__, t_diff);

        char log_msg[128];
        sprintf(log_msg, "CCD readout ends, total time used: %12.6f seconds\n", t_diff);
        write_log(fp_log_file, log_msg );
    }

    update_camera_status(0, log_camera_fname);

    fprintf(stdout, "******** fflush fp_log_file ********\n");
    fflush(fp_log_file);

    return 0;
}

void bonn_shutter_exposure_with_abort( 	uint32_t *gpio_reg, 
										uint32_t *gpio_in_reg,
										float exp_t_sec  ){

    char log_msg[128];
	fprintf(stdout, "[%s] --> open for %g seconds.\n", __func__, exp_t_sec);

    // integer part of exposure time
    uint32_t sleep_cnt_max  = (uint32_t)(exp_t_sec);
    uint32_t sleep_cnt      = 0;

    // decimal part of the exposure time
    float usec_f            = exp_t_sec - sleep_cnt_max;
    uint32_t usleep_cnt     = (uint32_t)(usec_f*1e6);

    gen_timestamp_op(timestamp0);
    gettimeofday(&start, NULL);
    
	bonn_shutter_open( gpio_reg, gpio_in_reg );

    usleep(usleep_cnt);

	while(1){
		if( (sleep_cnt >= sleep_cnt_max) || (global_abort_exposure == 1) ){
			break;
		}
		sleep_cnt++;
		sleep(1);
	}

    gen_timestamp_cl(timestamp1);
	gettimeofday(&end, NULL);

	bonn_shutter_close( gpio_reg, gpio_in_reg );

	if( global_abort_exposure == 1 ){
		fprintf(stdout, "[%s] --> shutter is closed due to 'exposure abort' signal!\n",__func__);
        write_log(fp_log_file, "shutter is closed due to 'exposure abort' signal!");
	}
	
    sprintf(log_msg, "exposure time %7.3f seconds reached, actual exposure is: %7.3f seconds", exp_t_sec, time_diff(&start,&end));
    write_log(fp_log_file, log_msg);
    fprintf(stdout, "%s\n", log_msg);
    
    usleep(279000); // 279ms delay before readout
}


void dark_frame_with_abort( float dark_t ){
    
    fprintf(stdout, "[%s]--> taking a dark frame of %g seconds ...\n", __func__, dark_t);
    char log_msg[128];
    sprintf(log_msg, "taking a dark frame of %g seconds ...", dark_t);
    write_log(fp_log_file, log_msg);
    
    uint32_t sleep_cnt_max  = (uint32_t)(dark_t);
    uint32_t sleep_cnt      = 0;

    float usec_f            = dark_t - sleep_cnt_max;
    uint32_t usleep_cnt     = (uint32_t)(usec_f*1e6);

    gen_timestamp_op(timestamp0);
    gettimeofday(&start, NULL);
    usleep(usleep_cnt);
    
    while(1){
        if( (sleep_cnt >= sleep_cnt_max) || (global_abort_exposure == 1) ){
            break;
        }
        sleep_cnt++;
        sleep(1);
    }

    gen_timestamp_cl(timestamp1);
    gettimeofday(&end, NULL);

    if( global_abort_exposure == 1 ){
        fprintf(stdout, "[%s]--> dark frame is aborted due to 'exposure abort' signal!\n",__func__);
        write_log(fp_log_file, "dark frame is aborted.");
    }
    else{
        sprintf(log_msg, "darktime %12.6f seconds reached, actual darktime is: %12.6f", dark_t, time_diff(&start,&end));
        write_log(fp_log_file, log_msg);
        fprintf(stdout, "%s", log_msg);
    }
}


